#include <QMap>
#include <QDebug>
#include "rule.h"
#include "core/sentence.h"

Rule::Rule(const Expression& pattern, const Expression& replacement)
  : _pattern(pattern)
  , _replacement(replacement)
  {
  }

void Rule::addCondition(Condition condition)
  {
  _conditions.append(Condition(condition));
  }

void Rule::addConditions(QList<Condition> conditions)
  {
  _conditions.append(conditions);
  }

bool Rule::apply(Expression & exp, Position pos = Position(-1,-1)) const
  {
  // prepare replacement
  Expression legalExpression(value());

  // teraz trzeba podmienić etykiety z reguł (%1, %2...) na prawdziwe
  int wordCount = _pattern.countWords();
  Expression invalidPart;
  exp.subExpression(pos.first, pos.second, wordCount, invalidPart);

  // sanity check if the subExpression method didn't screw up
  if (invalidPart.countWords() != _pattern.countWords())
    {
    qWarning() << "Invalid part has more or less words then key in the rule.";
    return false;
    }
  // zwroć listę indekserów, czyli np (%1, i)(%2, j)
  QMap<QString, QStringList> labelDict = getLabelDictionary(invalidPart);
  legalExpression.applyLabelDictionary(labelDict);

  exp.removeWords(pos.first, pos.second, wordCount);
  exp.insertIntoSentence(pos.first, pos.second, legalExpression);
  return true;
  }

// assuming that this Expression and other Expression have the same structure
// (the same number of sentences, same number of words of the same types, etc)
// the only thing that is different are the labels.

// this functions creates a map between those labels. For example, if
// this:  a_%1 b_%2
// other: a_i  b_j
// return map will be: {(%1, i), (%2, j)}

// if the relation between labels is one to many (e.g. a_%1 => a_{i j k l})
// labels with indexes are generated (%1.1, %1.2 and so on)

QMap<QString, QStringList> Rule::getLabelDictionary(Expression const &other) const
  {
  QMap<QString, QStringList> dict;
  for (size_t i = 0; i < _pattern.size(); i++)
    for (int j = 0; j < _pattern.at(i).size(); j++)
      {
      QStringList myLabels = _pattern[i].at(j)->getLabels();
      QStringList hisLabels = other[i].at(j)->getLabels();
      //if (myLabels.size() != hisLabels.size())
        {
        if (myLabels.size() == 1) // relation one to many
          {
          dict.insert(myLabels[0], hisLabels);
          // in this case we need to generate %1.1, %1.2 and so one
          for (int k = 0; k < hisLabels.size(); k++)
            {
            QString key = myLabels[0] + "." + QString::number(k);
            dict.insert(key, QStringList(hisLabels[k]));
            }
          }
        else
          qWarning() << "Size mismatch in labels " << myLabels << hisLabels;
        // Situation where there is several labels on the left and different several on the right
        // makes no sense - i cannot cocnlude dictionary from that.
        }
      }
  return dict;
  }

// Legacy comment
// part 1;
// kolejność zdań jest nieistotna
// więc trzeba najpierw sprawdzić ilość zdań w i.key()
// i dla każdego z nich przeszukać każde zdanie w _exp

/*
  part 2;
  mam już jedno zdanie (ZD) i szukam go w _exp

  dla każdego zdania w _exp
  szukam pierwszego słowa w ZD. Jeśli znajdę, sprawdzam czy następne w _exp jest
  takie samo jak w ZD. Sprawdzam tak aż do końca ZD.

  Jeśli dojadę do końca ZD - bingo!
  */
QPair<int, int> Rule::findPatternInExp(Expression const & exp) const
  {
  QPair<int, int> position(-1, -1);

  if (_pattern.size() > exp.size())
    return position;

  for (size_t i = 0; i < _pattern.size(); i++)
    {
    // let's take all corresponding sentences in exp and _pattern and compare them
    const Sentence &keySentence = _pattern.at(i);
    for (size_t j = 0; j < exp.size(); j++)
      {
      const Sentence &expSentence = exp.at(j);
      bool lastTimeTheyMatched = false;

      // to compare two Sentences we have to
      // take all corresponding words from both sentences compare their text only
      for (int k = 0, keyWordIndex = 0; (k < expSentence.size()) && (keyWordIndex < keySentence.size()); ++k)
        {
        if (expSentence.at(k)->getText() == keySentence.at(keyWordIndex)->getText() ) /* expWord is the same as keyWord */
          {
          lastTimeTheyMatched = true;
          if ((keyWordIndex + 1 == keySentence.size()) && (i + 1 == _pattern.size()))
            { // porównaliśmy ostatnie słowo w ostatnim zdaniu z wzoraca nielegalnego wyrażenia  - BINGO. Teraz trzeba zapamiętać dane.

            position.first = j - (_pattern.size()-1); // sentenceIndex
            position.second = k - (_pattern.at(0).size()-1); // wordIndex

            // let's check if other dependencies are met
            if (!checkForDependencies(exp, position.first, position.second))
              position = QPair<int, int>(-1,-1);
            }
          ++keyWordIndex;
          }
        else
          {
          if ((k + 1 == expSentence.size()))
            break;
          keyWordIndex = 0;  // szukamy znowu początku naszej frazy
          if (lastTimeTheyMatched)
            k--; // kiedy to trzeba wykonać, a kiedy nie?
          lastTimeTheyMatched = false;
          }
        } // for (int k = 0; k < expSentence->size(); ++k)
      } // for (int j = 0; j < exp.size(); j++)
    } // for (int i = 0; i < key()->size(); i++)

  return position;
  }

bool Rule::checkForDependencies(const Expression &exp, 
                                int startingSentence,
                                int startingWord) const
  {
  // check if there are repeating labels in the pattern 
  // for which we should look in exp.

  QMultiMap< QStringList, QPair<int, int> > labelsInPattern; // labels list, position
  // every LabelsList has its position
  // if the same LabelsList is in two places, it will have two positions in the map
  for (size_t i = 0; i < _pattern.size(); i++)
    for (int j = 0; j < _pattern.at(i).size(); j++)
      labelsInPattern.insert(_pattern.at(i).at(j)->getLabels(), QPair<int, int>(i, j));

  QMapIterator< QStringList, QPair<int, int> > it(labelsInPattern);
  while (it.hasNext())
    {
    it.next();
    // if the labelsList has more than one position
    // we must check if in the expression in all those positions labelsLists are the same
    if (labelsInPattern.values(it.key()).size() > 1)   // same as it.values().size() > 1
      {
      // position in exp  of first label (of group that should consist of same labels only)
      QPair<int, int> posInExp = computePositionInExp(startingSentence, startingWord, it.value());
      QStringList model = exp.at(posInExp.first).at(posInExp.second)->getLabels();

      QList< QPair<int, int> > otherWordsWithSameLabels = labelsInPattern.values(it.key());
      for (int i = 0; i < otherWordsWithSameLabels.size(); ++i)
        {
        // otherWordsWithSameLabels contains positions of Labels IN THE PATTERN
        // we have to cast it to postion in exp first!
        QPair<int, int> pos = computePositionInExp(startingSentence, startingWord, otherWordsWithSameLabels.at(i));
        if (exp.wordAt(pos).getLabels() != model)
          return false;
        }      
      }
    }

  // Conditions
  if (_conditions.size() > 0) 
    {
    foreach (Condition const & condition, _conditions)
      if (!checkCondition(exp, startingSentence, startingWord, condition))
        return false;
    }

  return true;
  }

bool Rule::checkCondition(const Expression &exp,
                          int startingSentence,
                          int startingWord, 
                          Condition const &condition) const
  {
  if (condition.arguments.size() != 2)
    return false;
  // possible arguments:
  //  * label id (%1, %2, etc)
  //  * label size (%1.size, %1.length)
  //  * label name (any string not starting with %)
  //  * number

  // lets cast label size to number
  //       and label id to name
  // from there it will be easier

  QString const &arg1 = condition.arguments.at(0);
  QString const &arg2 = condition.arguments.at(1);

  // hm... i  will need labels from exp corresponding to %1 and so on.
  QList <QString> labelsFromPattern;
  QList <QStringList> labelsFromExp;

  for (size_t i = 0; i < _pattern.size(); i++)
    for (int j = 0; j < _pattern.at(i).size(); j++)
      {
      labelsFromPattern.append(_pattern.at(i).at(j)->getLabels());
      labelsFromExp.append(exp.wordAt(
        computePositionInExp(startingSentence, startingWord, Position(i,j))
                                      ).getLabels());
      }
  // it MIGHT be smarter to do that only once instead
  // of once per each condition.

  bool firstIsANumber = false;
  bool secondIsANumber = false;

  
  int numberOne, numberTwo;
  bool conversionOk;

  numberOne = arg1.toInt(&conversionOk);
  if (!conversionOk && arg1.startsWith("%") && arg1.endsWith(".size"))
    {
    // so the first one is a number     
    firstIsANumber = true;
    QString argName = arg1.left(arg1.indexOf(".size"));
    numberOne = labelsFromExp[labelsFromPattern.indexOf(argName)].size();
    }
  else if (conversionOk)    
    firstIsANumber = true;

  numberTwo = arg2.toInt(&conversionOk);
  if (!conversionOk && arg2.startsWith("%") && arg2.endsWith(".size"))
    {
    // so the first one is a number     
    secondIsANumber = true;
    QString argName = arg2.left(arg2.indexOf(".size"));
    numberTwo = labelsFromExp[labelsFromPattern.indexOf(argName)].size();
    }
  else if (conversionOk)    
    secondIsANumber = true;

  if (secondIsANumber && firstIsANumber)
    {
    switch (condition.type)
      {
      case Condition::CT_EQUALS:
        return (numberOne == numberTwo);
      case Condition::CT_GREATER:
        return (numberOne > numberTwo);
      case Condition::CT_GREATER_OR_EQUAL:
        return (numberOne >= numberTwo);
      case Condition::CT_NOT_EQUAL:
        return (numberOne != numberTwo);
      }
    }
  else if (!secondIsANumber && !firstIsANumber)
    {
    switch (condition.type)
      {
      case Condition::CT_EQUALS:
        return (arg1 == arg2);
      case Condition::CT_GREATER:
        return (arg1 > arg2);
      case Condition::CT_GREATER_OR_EQUAL:
        return (arg1 >= arg2);
      case Condition::CT_NOT_EQUAL:
        return (arg1 != arg2);
      }
    }
  else
    qWarning() << "Error in conditon. One argument is a number, but the other one is not. "
               << arg1 << arg2;
  return false;
  }

QPair<int, int> Rule::computePositionInExp(int startingSentence, int startingWord,
                                           QPair<int, int> positionInPattern) const
  {
  QPair<int, int> posInExp;
  posInExp.first = positionInPattern.first + startingSentence;
      
  if (positionInPattern.first > 0)
    posInExp.second = positionInPattern.second;
  else
    posInExp.second = positionInPattern.second + startingWord;

  return posInExp;
  }
